using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class EdgesGeometry : BufferGeometry
    {
        public class Parameters
        {
            public BufferGeometry geometry;
            public double thresholdAngle;
        }
        public class EdgeDataItem
        {
            public int index0;
            public int index1;
            public Vector3 normal;
        }
        #region scope properties or methods
        //private static Vector3 _v0 = new Vector3();
        //private static Vector3 _v1 = new Vector3();
        //private static Vector3 _normal = new Vector3();
        //private static Triangle _triangle = new Triangle();
        private static EdgesGeometryContext getContext()
        {
            return ThreeThreadContext.GetCurrThreadContext().EdgesGeometryCtx;
        }
        #endregion

        #region Properties

        public Parameters parameters;

        #endregion

        #region constructor
        public EdgesGeometry(BufferGeometry geometry = null, double thresholdAngle = 1)
        {
            this.type = "EdgesGeometry";
            this.parameters = new Parameters()
            {
                geometry = geometry,
                thresholdAngle = thresholdAngle
            };
            if (geometry != null)
            {
                var ctx = getContext();
                var _triangle = ctx._triangle;
                var _normal = ctx._normal;
                var _v0 = ctx._v0;
                var _v1 = ctx._v1;
                var precisionPoints = 4;
                var precision = Math.Pow(10, precisionPoints);
                var thresholdDot = Math.Cos(MathEx.DEG2RAD * thresholdAngle);
                var indexAttr = geometry.getIndex();
                var positionAttr = geometry.attributes.position;
                var indexCount = indexAttr != null ? indexAttr.count : positionAttr.count;
                var indexArr = new int[] { 0, 0, 0 };
                var vertKeys = new string[] { "a", "b", "c" };
                var hashes = new string[3];
                var edgeData = new JsObj<EdgeDataItem>();
                var vertices = new ListEx<double>();
                for (int i = 0; i < indexCount; i += 3)
                {
                    if (indexAttr != null)
                    {
                        indexArr[0] = indexAttr.getIntX(i);
                        indexArr[1] = indexAttr.getIntX(i + 1);
                        indexArr[2] = indexAttr.getIntX(i + 2);
                    }
                    else
                    {
                        indexArr[0] = i;
                        indexArr[1] = i + 1;
                        indexArr[2] = i + 2;
                    }

                    var a = _triangle.A;
                    var b = _triangle.B;
                    var c = _triangle.C;
                    a.FromBufferAttribute(positionAttr, indexArr[0]);
                    b.FromBufferAttribute(positionAttr, indexArr[1]);
                    c.FromBufferAttribute(positionAttr, indexArr[2]);
                    _triangle.GetNormal(_normal);
                    // create hashes for the edge from the vertices
                    hashes[0] = $"{Math.Round(a.X * precision)},{Math.Round(a.Y * precision)},{Math.Round(a.Z * precision)}";
                    hashes[1] = $"{Math.Round(b.X * precision)},{Math.Round(b.Y * precision)},{Math.Round(b.Z * precision)}";
                    hashes[2] = $"{Math.Round(c.X * precision)},{Math.Round(c.Y * precision)},{Math.Round(c.Z * precision)}";
                    // skip degenerate triangles
                    if (hashes[0] == hashes[1] || hashes[1] == hashes[2] || hashes[2] == hashes[0])
                    {
                        continue;
                    }
                    // iterate over every edge
                    for (int j = 0; j < 3; j++)
                    {
                        // get the first and next vertex making up the edge
                        var jNext = (j + 1) % 3;
                        var vecHash0 = hashes[j];
                        var vecHash1 = hashes[jNext];

                        var v0 = _triangle.GetVector(vertKeys[j]);
                        var v1 = _triangle.GetVector(vertKeys[jNext]);
                        var hash = $"{vecHash0}_{vecHash1}";
                        var reverseHash = $"{vecHash1}_{vecHash0}";
                        if (edgeData.ContainsKey(reverseHash) && edgeData[reverseHash] != null)
                        {
                            // if we found a sibling edge add it into the vertex array if
                            // it meets the angle threshold and delete the edge from the map.
                            if (_normal.Dot(edgeData[reverseHash].normal) <= thresholdDot)
                            {
                                vertices.Push(v0.X, v0.Y, v0.Z);
                                vertices.Push(v1.X, v1.Y, v1.Z);
                            }
                            edgeData[reverseHash] = null;
                        }
                        else if (!(edgeData.ContainsKey(hash)))
                        {
                            // if we"ve already got an edge here then skip adding a new one
                            edgeData[hash] = new EdgeDataItem()
                            {
                                index0 = indexArr[j],
                                index1 = indexArr[jNext],
                                normal = _normal.Clone(),
                            };
                        }
                    }
                }
                // iterate over all remaining, unmatched edges and add them to the vertex array
                foreach (var kvp in edgeData)
                {
                    var key = kvp.Key;
                    var value = kvp.Value;
                    if (value != null)
                    {
                        var index0 = value.index0;
                        var index1 = value.index1;
                        _v0.FromBufferAttribute(positionAttr, index0);
                        _v1.FromBufferAttribute(positionAttr, index1);
                        vertices.Push(_v0.X, _v0.Y, _v0.Z);
                        vertices.Push(_v1.X, _v1.Y, _v1.Z);
                    }
                }
                this.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            }
        }
        #endregion

        #region methods
        public EdgesGeometry copy(EdgesGeometry source)
        {
            base.copy(source);
            this.parameters = new Parameters()
            {
                geometry = source.parameters.geometry,
                thresholdAngle = source.parameters.thresholdAngle,
            };
            return this;
        }

        #endregion
    }
}
